/*
 * Copyright 2017, Data61
 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
 * ABN 41 687 119 230.
 *
 * This software may be distributed and modified according to the terms of
 * the GNU General Public License version 2. Note that NO WARRANTY is provided.
 * See "LICENSE_GPLv2.txt" for details.
 *
 * @TAG(DATA61_GPL)
 */
#include <autoconf.h>

#include "benchmark.h"
#include "processing.h"
#include "json.h"

#include <irq.h>
#include <sel4platsupport/device.h>
#include <stdio.h>
#include <utils/time.h>

#define TRACE_POINT_OVERHEAD 0
#define TRACE_POINT_IRQ_PATH_START 1
#define TRACE_POINT_IRQ_PATH_END 2

#ifndef CONFIG_MAX_NUM_TRACE_POINTS
#define CONFIG_MAX_NUM_TRACE_POINTS 0
#endif

static ccnt_t kernel_log_data[KERNEL_MAX_NUM_LOG_ENTRIES];
static unsigned int offsets[CONFIG_MAX_NUM_TRACE_POINTS];
static unsigned int sizes[CONFIG_MAX_NUM_TRACE_POINTS];

static json_t *
process(void *results) {
     irq_results_t *irq_results = (irq_results_t *) results;

    /* Sort and group data by tracepoints. A stable sort is used so the first N_IGNORED
     * results of each tracepoint can be ignored, as this keeps the data in chronological
     * order.
     */
    logging_stable_sort_log(irq_results->kernel_log, irq_results->n);
    logging_group_log_by_key(irq_results->kernel_log, irq_results->n, sizes, offsets, CONFIG_MAX_NUM_TRACE_POINTS);

    /* Copy the cycle counts into a separate array to simplify further processing */
    for (int i = 0; i < irq_results->n; ++i) {
        kernel_log_data[i] = kernel_logging_entry_get_data(&irq_results->kernel_log[i]);
    }

    /* Process log entries generated by an "empty" tracepoint, which recorded
     * the number of cycles between starting a tracepoint and stopping it
     * immediately afterwards. This will determine the overhead introduced by
     * using tracepoints.
     */
    int n_overhead_data = sizes[TRACE_POINT_OVERHEAD] - N_IGNORED;
    if (n_overhead_data <= 0) {
        ZF_LOGF("Insufficient data recorded. Was the kernel built with the relevant tracepoints?\n");
    }

    ccnt_t *overhead_data = &kernel_log_data[offsets[TRACE_POINT_OVERHEAD] + N_IGNORED];

    /* The results of the IRQ path benchmark are split over multiple tracepoints.
     * A new buffer is allocated to store the amalgamated results. */
    int n_data = sizes[TRACE_POINT_IRQ_PATH_START] - N_IGNORED;
    if (n_data <= 0) {
        ZF_LOGF("Insufficient data recorded. Was the kernel built with the relevant tracepoints?\n");
    }

    ccnt_t *data = (ccnt_t*)malloc(sizeof(ccnt_t) * n_data);
    if (data == NULL) {
        ZF_LOGF("Failed to allocate memory\n");
    }

    json_t *array = json_array();
    result_desc_t desc = {0};
    result_t result = process_result(n_overhead_data, overhead_data, desc);

    result_set_t set = {
        .name = "Tracepoint overhead",
        .n_results = 1,
        .results = &result,
    };

    json_array_append_new(array, result_set_to_json(set));

    /* Add the results from the IRQ path tracepoints to get the total IRQ path cycle counts.
     * The average overhead is subtracted from each cycle count (doubled as there are 2
     * tracepoints) to account for overhead added to the cycle counts by use of tracepoints.
     */
    ccnt_t *starts = &kernel_log_data[offsets[TRACE_POINT_IRQ_PATH_START] + N_IGNORED];
    ccnt_t *ends = &kernel_log_data[offsets[TRACE_POINT_IRQ_PATH_END] + N_IGNORED];
    for (int i = 0; i < n_data; ++i) {
        data[i] = starts[i] + ends[i] - (result.mean * 2);
    }

    set.name = "IRQ Path Cycle Count (accounting for overhead)";

    result = process_result(n_data, data, desc);
    json_array_append_new(array, result_set_to_json(set));

    free(data);

    return array;
}

static benchmark_t irq_benchmark = {
    .name = "irq",
    .enabled = config_set(CONFIG_APP_IRQBENCH) && CONFIG_MAX_NUM_TRACE_POINTS == 3,
    .results_pages = BYTES_TO_SIZE_BITS_PAGES(sizeof(irq_results_t), seL4_PageBits),
    .process = process,
    .init = blank_init
};

benchmark_t *
irq_benchmark_new(void)
{
    return &irq_benchmark;
}

static json_t *
irquser_process(void *r) {
    irquser_results_t *raw_results = r;

    result_desc_t desc = {
        .ignored = N_IGNORED,
        .name = "IRQ user measurement overhead"
    };

    result_t results[3];

    results[0] = process_result(N_RUNS, raw_results->overheads, desc);

    desc.overhead = results[0].min;

    results[1] = process_result(N_RUNS, raw_results->thread_results, desc);
    results[2] = process_result(N_RUNS, raw_results->process_results, desc);

    char *types[] = {"Measurement overhead", "Without context switch", "With context switch"};

    column_t col = {
        .header = "Type",
        .type = JSON_STRING,
        .string_array = types
    };

    result_set_t set = {
        .name = "IRQ path cycle count (measured from user level)",
        .n_results = 3,
        .results = results,
        .n_extra_cols = 1,
        .extra_cols = &col
    };

    json_t *json = json_array();
    json_array_append_new(json, result_set_to_json(set));
    return json;
}

static benchmark_t irquser_benchmark = {
    .name = "irquser",
    .enabled = config_set(CONFIG_APP_IRQUSERBENCH),
    .results_pages = BYTES_TO_SIZE_BITS_PAGES(sizeof(irquser_results_t), seL4_PageBits),
    .process = irquser_process,
    .init = blank_init
};

benchmark_t *
irquser_benchmark_new(void)
{
   return &irquser_benchmark;
}
